<?php
require_once('libraries/uuid.php');

/**
 * Базовый класс модели, имплементирующий общий функционал
 */
class AbstractModel{

  /**
   * Хранит данные модели в виде ассоциированного массива.
   * Ключи, которые НЕ перечислены в этом массиве - не будут добавляться.
   * То есть необходимо при описании модели перечислить все столбцы.
   * @var array 
   */
  public $data = array();

  /**
   * Содержит информацию о типах атрибутов модели.
   * Ключ - имя атрибута
   * Значение - тип ('date', 'int', 'uuid').
   * По умолчанию, если не указать, то будет устанавливаться так как приходит из БД
   * @var array
   */
  protected $columnTypes = array();

  /**
   * Содержит информацию о названии таблицы, к которой привязана модель.
   * @var string
   */
  protected $tableName = '';

  /**
   * Содержит перечень полей являющихся primary key у таблицы.
   * @var array
   */
  protected $primaryKey = array();

  /**
   * Конструктор позволяет установить атрибуты модели сразу, передав их массивом
   * @param arrray $data
   */
  function __construct($data = array()){
		$this->setObjectDataFromArray($data);
	}

	
	/**
   * Устанавливает атрибуты модели из массива
   * @param array $data 
   */
  function setObjectDataFromArray($data){
		if (is_array($data)){
			foreach($data as $key=>$value){
				if (key_exists($key, $this->data)){
					$this->setProperty($key, $value);
				}
			}
		}
	}

	/**
   * Установка одного атрибута модели с приведением к типам.
   * @param <type> $key - Атрибут
   * @param <type> $value - Значение
   */
  protected function setProperty($key, $value){
    if (key_exists($key, $this->columnTypes)){
      if ($this->columnTypes[$key] == 'date') $value = strtotime($value);
      if ($this->columnTypes[$key] == 'int') $value = intval($value);
      if ($this->columnTypes[$key] == 'bool') $value = ($value=='t'||$value==true)?true:false;
    }
    $this->data[$key] = $value;
	}
	

  /**
  * Сохраняет модель
  * @return bool
  */
  public function save() {
    // получаем соединение
		$db = DBFacade::getConnection();
    // генерим поля для обновления
    $fields = array();
    $datakeys = array_diff(array_keys($this->data), $this->primaryKey);
    foreach( $datakeys as $key ) {
      // добавляем поля к запросу
      $fields[] = "{$key} = :{$key}";
    }
    // генерим поля для первичного ключа
    $primary = array();
    foreach( $this->primaryKey as $value ) {
      $primary[] = "{$value} = :{$value}";
    }
    // генерим запрос
    $sql = "UPDATE {$this->tableName} SET ";
    $sql .= implode(', ', $fields);
    $sql .= ' WHERE ';
    $sql .= implode(' AND ', $primary);

    // обрабатываем данные для вставки
    $input = array();
    foreach( $this->data as $key=>$value ) {
      if( isset( $this->columnTypes[$key] ) ) {
        switch( $this->columnTypes[$key] ) {
          case 'bool': {
            $input[$key] = ($this->data[$key]==true)?'t':'f';
          } break;
          default: {
            $input[$key] = $this->data[$key];
          } break;
        }
      } else {
        $input[$key] = $this->data[$key];
      }
    }

    // выполняем запрос
    try{
      $dbh = $db->prepare($sql);
      $result = $dbh->execute($input);
      if ($result) return true;
      else return false; //сохранение не вышло
    }
    catch (Exception $e){ // Ошибка выполнения SQL команды
      Logger::error("save failed:\n".var_export($e)."\n".var_export($this));
      return false;
    }

    return false;
  }

  /**
  * Выполняет запрос на вставку записи в БД
  * @return bool
  */
  public function insert() {
    // получаем соединение
    $db = DBFacade::getConnection();
    // проверяем заполнен ли первичный ключ
    $key_full = true;
    foreach( $this->primaryKey as $key ) {
      if( empty($this->data[$key]) ) {
        // пробуем сгенерить ключ (только для специфических типов полей)
        switch($this->columnTypes[$key]) {
          case 'uuid': {
            $this->data[$key] = newUUID();
          } break;
        }
      }
    }
    // генерим поля для вставки
    $fields = array();
    $values = array();
    foreach( $this->data as $key=>$value ) {
      $fields[] = $key;
      $values[] = ":{$key}";
    }
    // генерим запрос
    $sql = "INSERT INTO {$this->tableName} (";
    $sql .= implode(', ', $fields);
    $sql .= ') VALUES (';
    $sql .= implode(', ', $values);
    $sql .= ');';

    // обрабатываем данные для вставки
    $input = array();
    foreach( $this->data as $key=>$value ) {
      if( isset( $this->columnTypes[$key] ) ) {
        switch( $this->columnTypes[$key] ) {
          case 'bool': {
            $input[$key] = ($this->data[$key]==true)?'t':'f';
          } break;
          default: {
            $input[$key] = $this->data[$key];
          } break;
        }
      } else {
        $input[$key] = $this->data[$key];
      }
    }

    // выполняем запрос
    try{
      $dbh = $db->prepare($sql);
      $result = $dbh->execute($input);
      if( $result ) {
        return $this->reload();
      } else {
        return false; //инсерт не удался
      }
    }
    catch (Exception $e){ // Ошибка выполнения SQL команды
      Logger::error("insert failed:\n".var_export($e)."\n".var_export($this));
      return false;
    }
  }

  /**
   * Создает экземпляр модели в БД
   * @param array $data
   * @return object
   */
  static public function create( $data ) {
    $class = get_called_class();
    $object = new $class($data);
    $result = $object->insert();
    if( $result ) {
      return $object;
    } else {
      return false;
    }
  }

  /**
   * Перезагружаем модель из БД
   * @return bool
   */
  private function reload() {
    $db = DBFacade::getConnection();
    // генерим поля для первичного ключа
    $primary = array();
    $fields = array();
    foreach( $this->primaryKey as $value ) {
      $primary[] = "{$value} = :{$value}";
      $fields[$value] = $this->data[$value];
    }
    // собираем запрос
    $sql = "SELECT * FROM {$this->tableName} WHERE ";
    $sql .= implode(' AND ', $primary);

    // выполняем запрос
    try{
      $dbh = $db->prepare($sql);
      $dbh->execute( $fields );
      $line = $dbh->fetch();
      // переопределяем поля
      $this->setObjectDataFromArray($line);
      // выдаем ответ
      return true;
    }
    catch (Exception $e){ // Ошибка выполнения SQL команды
      Logger::error("reload failed:\n".var_export($e)."\n".var_export($this));
      return false;
    }
  }

  /**
  * Удаляем запись модели из БД
  * @return bool
  */
  public function delete() {
    $db = DBFacade::getConnection();
    // генерим поля для первичного ключа
    $primary = array();
    $fields = array();
    foreach( $this->primaryKey as $value ) {
      $primary[] = "{$value} = :{$value}";
      $fields[$value] = $this->data[$value];
    }
    // собираем запрос
    $sql = "DELETE FROM {$this->tableName} WHERE ";
    $sql .= implode(' AND ', $primary);

    // выполняем запрос
    try{
      $dbh = $db->prepare($sql);
      $dbh->execute( $fields );
      // выдаем ответ
      return true;
    }
    catch (Exception $e){ // Ошибка выполнения SQL команды
      Logger::error("delete failed:\n".var_export($e)."\n".var_export($this));
      return false;
    }
  }
}